package ycl.important.service.impl.login;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ycl.important.common.constants.RedisConstant;
import ycl.important.common.models.req.base.AccountPassReq;
import ycl.important.common.models.res.login.LoginRes;
import ycl.important.common.models.req.login.RegisterReq;
import ycl.important.common.models.req.login.SmsLoginReq;
import ycl.important.common.utils.BeanConvertUtil;
import ycl.important.common.utils.CryptoUtil;
import ycl.important.common.utils.RedisOperationUtil;
import ycl.important.entity.sys.User;
import ycl.important.mapper.sys.UserMapper;
import ycl.important.service.login.LoginService;
import ycl.important.service.sys.UserFriendsService;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 登录 逻辑处理器
 *
 * @author: YCL
 * @date: 2021-06-14 22:21:33
 */
@Service("loginService")
public class LoginServiceImpl
		implements LoginService {

	@Resource
	private UserFriendsService userFriendsService;
	@Resource
	private UserMapper userMapper;
	@Resource
	private RedisOperationUtil redisOperationUtil;
	@Resource
	private StringRedisTemplate stringRedisTemplate;

	@Override
	public void logout(HttpServletRequest request) {
		LoginRes user = redisOperationUtil.getLoginUser(request);
		userMapper.logout(user.getId());
		redisOperationUtil.logout(request);
	}

	@Override
	public LoginRes loginByUsernamePassword(AccountPassReq dto) {
		String username = dto.getAccount();
		redisOperationUtil.userLoginIsLock(username);
		User user = userMapper.selectByUsername(username);
		return login(user, dto.getPassword(), username);
	}

	@Override
	public LoginRes loginByMobilePassword(AccountPassReq dto) {
		String mobile = dto.getAccount();
		redisOperationUtil.userLoginIsLock(mobile);
		User user = userMapper.selectByMobile(mobile);
		return login(user, dto.getPassword(), mobile);
	}

	@Override
	public LoginRes loginBySms(SmsLoginReq dto) {
		String mobile = dto.getMobile();
		String key = RedisConstant.SMS_USER_LOGIN + mobile;
		String code = stringRedisTemplate.opsForValue().get(key);
		Assert.isTrue(dto.getAuthCode().equals(code), "验证码不正确");
		User user = userMapper.selectByMobile(mobile);
		Assert.notNull(user, "没有此用户");
		//删除用户验证码
		stringRedisTemplate.delete(key);
		return login(user, mobile);
	}

	private LoginRes login(User user, String password, String lockTitle) {
		Assert.notNull(user, "没有此用户");
		Assert.isTrue(user.getStatus().equals(0), "用户被禁用不可登录");
		Assert.isFalse(user.getLocked(), "用户被锁住不可登录");
		String crypto = CryptoUtil.cryptoPassword(password, user.getSalt());
		if (! crypto.equals(user.getPassword())) {
			redisOperationUtil.recordLoginFailMsg(lockTitle);
			Assert.isTrue(false, "密码不正确");
		}
		return login(user, lockTitle);
	}

	private LoginRes login(User user, String lockTitle) {
		String password = user.getPassword();

		//删除用户之前登录的信息
		String old = user.getToken();
		if (StrUtil.isNotBlank(old))
			stringRedisTemplate.delete(RedisConstant.USER_LOGIN + old);

		LoginRes vo = BeanConvertUtil.convert(user, LoginRes.class);
		String token = CryptoUtil.cryptoToken(password);
		vo.setToken(token);
		userMapper.setTokenById(user.getId(), token);
		//保存用户登录信息
		stringRedisTemplate.opsForValue().set(RedisConstant.USER_LOGIN + token, JSONObject.toJSONString(vo), RedisConstant.LOGIN_EFFECTIVE_TIME, TimeUnit.DAYS);
		//删除登录失败次数信息
		stringRedisTemplate.delete(RedisConstant.USER_LOGIN_FAIL + lockTitle);
		return vo;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void register(RegisterReq dto) {
		String mobile = dto.getMobile();
		String code = stringRedisTemplate.opsForValue().get(RedisConstant.USER_REGISTER + mobile);
		Assert.isTrue(dto.getAuthCode().equals(code), "验证码不正确");

		Assert.isFalse(userMapper.selectExistByMobile(mobile), "手机号已被注册");
		Assert.isFalse(userMapper.selectExistByMobile(dto.getUsername()), "用户名已存在");
		List<Long> userIds = new ArrayList<>();
		User user = BeanConvertUtil.convert(dto, User.class);
		String salt = RandomUtil.randomStringUpper(20);
		while (true) {
			String invitationCode = RandomUtil.randomStringUpper(8);
			boolean b = userMapper.selectExistByInvitationCode(invitationCode);
			if (! b) {
				user.setInvitationCode(invitationCode);
				break;
			}
		}
		String crypto = CryptoUtil.cryptoPassword(dto.getPassword(), salt);
		user.setPassword(crypto);
		user.setSalt(salt);
		if (StrUtil.isNotBlank(dto.getInvitation())) {
			User parent = userMapper.selectByInvitationCode(dto.getInvitation());
			if (parent != null) {
				user.setParentId(parent.getParentId());
				userIds.add(parent.getId());
			}
		}
		userMapper.insert(user);
		userIds.add(user.getId());
		userFriendsService.addFriends(userIds);
		stringRedisTemplate.delete(RedisConstant.USER_REGISTER + mobile);
	}
}
